import base64
import binascii
import json
import logging
import re,os
import uuid
from Crypto.Cipher import AES
from django.db.models import Q
from datetime import datetime
from django.conf import settings
from django.http import Http404
from django.db import models 
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
from django.core.files import File
import requests
from rest_framework.views import APIView
from rest_framework.decorators import api_view,permission_classes
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.exceptions import PermissionDenied
from django.core.cache import cache
from rest_framework import generics, permissions, status, filters
from rest_framework.response import Response
from django.utils import timezone
from datetime import timedelta
from rest_framework.permissions import IsAuthenticatedOrReadOnly,IsAuthenticated,AllowAny
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from apps.orders.permissions import IsOrderOwnerOrChef
from apps.payments.serializers import ChefWalletSerializer
from .models import Chef, ChefReview, DjChef, DjChefVerification, VerificationRecord
from apps.orders.models import Order
from apps.schedules.models import TimeSlot
from .models import Dish, DishCategory
from .serializers import (
    ChefConsumerSerializer,
    ChefAuthVerifySerializer,
    ChefCategorySerializer,
    ChefReviewSerializer,
    DjChefProfileSerializer,
    DishSerializer,
    PhoneNumberSerializer,
    SMSCodeSerializer,
    VdjChefDetailSerializer,
    VdjChefVerificationSerializer,
    WechatLoginSerializer,
    LoginResponseSerializer,
    ChangePhoneSerializer,
    VerificationImageSerializer,
    VerificationSubmitSerializer,
    VdjDishCategorySerializer,
    VdjChefSerializer, 
    VdjChefReviewSerializer,
    VdjChefProfileSerializer,
    VdjDishSerializer,
    VdjPhoneNumberSerializer,
    VdjSMSCodeSerializer,
    VdjWechatLoginSerializer,
    VdjLoginResponseSerializer,
    VdjChangePhoneSerializer,
    VdjVerificationImageSerializer,
    VdjVerificationSubmitSerializer,
    VdjVerificationStatusSerializer    
)
import random
from django.contrib.auth import get_user_model
from apps.orders.serializers import (
    OrderSerializer,
    OrderCreateSerializer,
    TimeSlotSerializer
)
from .filters import ChefFilter
from .permissions import IsReviewOwnerOrReadOnly
from apps.chefs import serializers



logger = logging.getLogger(__name__)

class ChefListView(generics.ListAPIView):
    queryset = DjChef.objects.all()
    serializer_class = ChefConsumerSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter]
    filterset_class = ChefFilter
    pagination_class = PageNumberPagination
    
    def get_queryset(self):
        queryset = super().get_queryset()
        city = self.request.query_params.get('city')
        
        # 过滤出可接单且已认证的厨师
        queryset = queryset.filter(
            user_status='active',
            verification_status='verified'
        )
        
        if city:
            # 更宽松的城市匹配方式
            city = city.replace('市', '').strip()
            queryset = queryset.filter(
                Q(city__icontains=city) |
                Q(city__startswith=city) |
                Q(city__endswith=city) |
                Q(service_area__icontains=city)  # 同时匹配服务区域
            )
        
        # 按评分和完成订单数排序，优先展示优质厨师
        return queryset.order_by('-rating', '-completed_orders').distinct()
    
    def list(self, request, *args, **kwargs):
        try:
            return super().list(request, *args, **kwargs)
        except Exception as e:
            print(f"API处理错误: {str(e)}")
            return Response(
                {"error": "服务器内部错误", "details": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

class ChefDetailView(generics.RetrieveAPIView):
    queryset = DjChef.objects.all().select_related('user')  # 仅预加载存在的关联字段
    serializer_class = ChefConsumerSerializer
    lookup_field = 'id'

    def get_object(self):
        lookup_value = self.kwargs.get(self.lookup_field)
        logger.info(f"请求的厨师ID: {lookup_value}，使用DjChef模型查询")

        try:
            obj = super().get_object()  # 获取DjChef对象

            # 处理头像URL（本身是URL字符串，无需.url）
            if not obj.avatar:  # 若未设置头像，使用默认URL
                obj.avatar = f"{settings.MEDIA_URL}default_avatar.png"
                logger.warning(f"DjChef ID={lookup_value} 未设置头像，使用默认图片")

            # 处理背景图URL（根据审核状态判断）
            if not obj.background_approved or not obj.background:
                # 审核未通过或无背景图，使用默认URL
                obj.background = f"{settings.MEDIA_URL}default_background.png"
                if not obj.background_approved:
                    logger.info(f"DjChef ID={lookup_value} 背景图未通过审核，使用默认图片")
                else:
                    logger.warning(f"DjChef ID={lookup_value} 未设置背景图，使用默认图片")

            logger.info(f"成功查询到DjChef数据: ID={obj.id}")
            return obj

        except DjChef.DoesNotExist:
            logger.error(f"未找到ID为 {lookup_value} 的DjChef数据")
            raise  # 抛出404异常
        except Exception as e:
            logger.error(f"查询DjChef数据时发生错误: {str(e)}", exc_info=True)
            raise

class ChefReviewCreateView(generics.CreateAPIView):
    queryset = ChefReview.objects.all()
    serializer_class = ChefReviewSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        chef = Chef.objects.get(id=self.kwargs['chef_id'])
        serializer.save(user=self.request.user, chef=chef)
        
        # Update chef's rating
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        chef.save()

class ChefReviewUpdateDeleteView(generics.RetrieveUpdateDestroyAPIView):
    queryset = ChefReview.objects.all()
    serializer_class = ChefReviewSerializer
    permission_classes = [IsReviewOwnerOrReadOnly]
    lookup_field = 'id'

    def perform_update(self, serializer):
        instance = serializer.save()
        
        # Update chef's rating
        chef = instance.chef
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        chef.save()

    def perform_destroy(self, instance):
        chef = instance.chef
        instance.delete()
        
        # Update chef's rating
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        if chef.review_count > 0:
            chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        else:
            chef.rating = 0
        chef.save()

class ChefTimeSlotView(generics.GenericAPIView):
    """Get available time slots for a chef"""
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    
    def get(self, request, *args, **kwargs):
        chef_id = self.kwargs['chef_id']
        try:
            chef = Chef.objects.get(id=chef_id)
        except Chef.DoesNotExist:
            return Response({'error': 'Chef not found'}, status=status.HTTP_404_NOT_FOUND)
            
        now = timezone.now()
        time_slots = self.generate_time_slots(chef, now)
        serializer = TimeSlotSerializer(time_slots, many=True)
        return Response(serializer.data)
    
    def generate_time_slots(self, chef, now):
        slots = []
        current_hour = now.hour
        days_to_add = 0
        
        meal_types = [
            {'type': 'lunch', 'text': 'Lunch', 'hour': 11},
            {'type': 'dinner', 'text': 'Dinner', 'hour': 17}
        ]
        
        # Determine starting day
        if current_hour + 4 <= 11:
            days_to_add = 0
        elif current_hour + 4 <= 17:
            days_to_add = 0
            meal_types = meal_types[1:]  # Skip today's lunch
        else:
            days_to_add = 1
            now += timedelta(days=1)
        
        # Generate 6 time slots
        for i in range(3):
            date = now + timedelta(days=i)
            for meal in meal_types:
                if len(slots) < 6:
                    slot_time = date.replace(hour=meal['hour'], minute=0, second=0, microsecond=0)
                    slots.append({
                        'id': f"{date.date()}-{meal['type']}",
                        'display': f"{self.format_date(date)} {meal['text']}",
                        'value': slot_time.isoformat()
                    })
        return slots
    
    def format_date(self, date):
        today = timezone.now().date()
        tomorrow = today + timedelta(days=1)
        
        if date.date() == today:
            return 'Today'
        elif date.date() == tomorrow:
            return 'Tomorrow'
        else:
            return date.strftime('%m/%d')

class OrderCreateView(generics.CreateAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderCreateSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def perform_create(self, serializer):
        # Validate chef availability
        chef = serializer.validated_data['chef']
        scheduled_time = serializer.validated_data['scheduled_time']
        
        # Check if chef is available at this time
        if not self.is_chef_available(chef, scheduled_time):
            raise serializers.ValidationError('Chef is not available at this time')
        
        order = serializer.save(user=self.request.user)
        
        # Update chef stats
        chef.completed_orders += 1
        chef.total_earnings += order.total_amount
        chef.save()
        
        return order
    
    def is_chef_available(self, chef, scheduled_time):
        # Check if chef is marked as available
        if not chef.is_available:
            return False
            
        # Check if chef has overlapping orders
        buffer_before = scheduled_time - timedelta(hours=2)
        buffer_after = scheduled_time + timedelta(hours=4)
        
        overlapping_orders = Order.objects.filter(
            chef=chef,
            scheduled_time__range=(buffer_before, buffer_after),
            status__in=['confirmed', 'preparing', 'delivering']
        ).exists()
        
        return not overlapping_orders

class OrderDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    permission_classes = [permissions.IsAuthenticated, IsOrderOwnerOrChef]
    
    def perform_update(self, serializer):
        instance = serializer.save()
        # Add notification logic here if status changed
        if 'status' in serializer.validated_data:
            self.send_status_notification(instance)
    
    def send_status_notification(self, order):
        # Implement notification logic (email, push, etc.)
        pass

class UserOrderListView(generics.ListAPIView):
    serializer_class = OrderSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Order.objects.filter(user=self.request.user).order_by('-created_at')

class ChefOrderListView(generics.ListAPIView):
    serializer_class = OrderSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Order.objects.filter(chef__user=self.request.user).order_by('-created_at')
    

class ChefCategoryListView(generics.ListAPIView):
    """
    获取厨师提供的菜品分类列表
    GET /api/chefs/chefs/{chef_id}/categories/
    """
    serializer_class = ChefCategorySerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_queryset(self):
        chef_id = self.kwargs['chef_id']
        
        # 验证厨师是否存在
        try:
            Chef.objects.get(pk=chef_id)
        except Chef.DoesNotExist:
            raise Http404("厨师不存在")
            
        return DishCategory.objects.filter(
            dishes__chef_id=chef_id
        ).annotate(
            dish_count=models.Count('dishes')
        ).filter(
            dish_count__gt=0
        ).distinct().order_by('name')

class ChefDishListView(generics.ListAPIView):
    """
    获取厨师提供的菜品列表
    GET /api/chefs/chefs/{chef_id}/dishes/
    """
    serializer_class = DishSerializer  # 需要创建这个序列化器
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category']

    def get_queryset(self):
        chef_id = self.kwargs['chef_id']
        return Dish.objects.filter(
            chef_id=chef_id,
            is_available=True
        ).select_related('category').order_by('name')
    
@api_view(['GET'])
def chef_menu_view(request, chef_id):
    """
    获取厨师完整菜单（分类+菜品）
    GET /api/consumer/chefs/{chef_id}/menu/
    """
    # 1. 获取分类数据
    categories = DishCategory.objects.filter(
        dishes__chef_id=chef_id,
        dishes__is_available=True
    ).distinct().annotate(
        dish_count=models.Count('dishes')
    ).filter(
        dish_count__gt=0
    ).order_by('name')
    
    # 2. 获取所有可用菜品
    dishes = Dish.objects.filter(
        chef_id=chef_id,
        is_available=True
    ).select_related('category')
    
    # 3. 构建响应数据结构
    data = {
        'chef_id': chef_id,
        'categories': ChefCategorySerializer(categories, many=True).data,
        'dishes_by_category': {
            str(cat.id): DishSerializer(
                dishes.filter(category=cat), 
                many=True
            ).data 
            for cat in categories
        }
    }
    
    return Response(data)


#厨师更改自己的接单状态

User = get_user_model()
# 修改后的视图代码（核心部分）
class ChefStatusUpdateView(APIView):
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        # 获取openid并查询厨师（沿用之前的逻辑）
        openid = request.data.get('openid')
        if not openid:
            return Response({'error': '缺少openid参数'}, status=400)
        
        try:
            dj_chef = DjChef.objects.get(openid=openid)
        except DjChef.DoesNotExist:
            return Response({'error': '当前用户不是厨师'}, status=403)
        
        # 验证认证状态
        if dj_chef.verification_status != 'verified':
            return Response({'error': '未通过厨师认证'}, status=403)
        
        new_status = request.data.get('status')
        if not new_status:
            return Response({'error': '缺少status参数'}, status=400)
        
        # 核心：当厨师上线（状态改为active）时，生成档期
        old_status = dj_chef.user_status
        if new_status == 'active' and old_status != 'active':
            # 生成档期（可配置数量，当前为20个）
            generated_count = self.generate_time_slots(dj_chef, target_count=20)
            logger.info(f"厨师{dj_chef.id}上线，生成{generated_count}个新档期")
        
        # 更新厨师状态（沿用之前的逻辑）
        dj_chef.user_status = new_status
        dj_chef.save()
        
        # 同步更新Chef模型（不变）
        try:
            chef_profile = Chef.objects.get(djchef=dj_chef)
            chef_profile.status = new_status
            chef_profile.save()
        except Chef.DoesNotExist:
            pass
        
        return Response(
            {'message': '状态更新成功', 'status': new_status},
            status=status.HTTP_200_OK
        )
    
    def generate_time_slots(self, chef, target_count=6):
        """
        生成/补充厨师的档期，确保最终有target_count个有效档期
        逻辑：从当前时间+4小时开始，筛选出符合条件的现有档期，不足则补充生成
        """
        try:
            current_time = timezone.now()
            start_time = current_time + timedelta(hours=20)  # 起始时间：当前时间+20小时
            logger.info(f"=== 开始处理档期 ===")
            logger.info(f"厨师ID: {chef.id}, 厨师昵称: {chef.nickname}")
            logger.info(f"当前时间: {current_time}, 起始计算时间: {start_time}")
            
            # 定义时段规则
            LUNCH_START = 11
            LUNCH_END = 14
            DINNER_START = 18
            DINNER_END = 20
            logger.info(f"时段规则 - 午餐: {LUNCH_START}-{LUNCH_END}点, 晚餐: {DINNER_START}-{DINNER_END}点")
            
            # 1. 计算理论上应该存在的档期列表（按时间顺序）
            theoretical_slots = []
            current_date = start_time.date()
            current_hour = start_time.hour
            logger.info(f"初始日期: {current_date}, 初始小时: {current_hour}")
            
            # 确定第一个时段
            if current_hour >= DINNER_START:
                current_date += timedelta(days=1)
                next_period = 'lunch'
                logger.info(f"当前时间晚于晚餐开始({DINNER_START}点)，第一个档期为次日午餐")
            elif current_hour >= LUNCH_START:
                next_period = 'dinner'
                logger.info(f"当前时间在午餐时段内({LUNCH_START}-{LUNCH_END}点)，第一个档期为当天晚餐")
            else:
                next_period = 'lunch'
                logger.info(f"当前时间早于午餐开始({LUNCH_START}点)，第一个档期为当天午餐")
            
            # 生成target_count个理论档期（日期+时段）
            for i in range(target_count):
                theoretical_slots.append((current_date, next_period))
                logger.debug(f"理论档期 {i+1}/{target_count}: {current_date} {next_period}")
                
                # 计算下一个档期
                if next_period == 'lunch':
                    next_period = 'dinner'
                else:
                    next_period = 'lunch'
                    current_date += timedelta(days=1)
            
            logger.info(f"生成理论档期共 {len(theoretical_slots)} 个")
            
            # 2. 查询现有有效档期（在理论档期范围内）
            existing_slots = set()
            for idx, (date, period) in enumerate(theoretical_slots):
                exists = TimeSlot.objects.filter(chef=chef, date=date, period=period).exists()
                if exists:
                    existing_slots.add((date, period))
                    logger.debug(f"现有档期 {idx+1}: {date} {period} 已存在")
                else:
                    logger.debug(f"现有档期 {idx+1}: {date} {period} 不存在")
            
            logger.info(f"发现现有档期 {len(existing_slots)} 个")
            
            # 3. 计算需要补充的档期（理论档期 - 现有档期）
            need_generate = [slot for slot in theoretical_slots if slot not in existing_slots]
            logger.info(f"需要补充生成的档期: {len(need_generate)} 个")
            for idx, (date, period) in enumerate(need_generate):
                logger.debug(f"待生成档期 {idx+1}: {date} {period}")
            
            # 4. 生成缺失的档期
            generated_count = 0
            for date, period in need_generate:
                try:
                    # 双重检查是否存在，避免并发问题
                    if not TimeSlot.objects.filter(chef=chef, date=date, period=period).exists():
                        slot = TimeSlot.objects.create(
                            chef=chef,
                            date=date,
                            period=period,
                            status='available'
                        )
                        generated_count += 1
                        logger.info(f"成功创建档期: ID={slot.id}, {date} {period}")
                    else:
                        logger.warning(f"档期已被创建(并发)，跳过: {date} {period}")
                except Exception as e:
                    logger.error(f"创建档期失败 {date} {period}: {str(e)}", exc_info=True)
            
            logger.info(f"=== 档期处理完成 ===")
            logger.info(f"目标生成 {target_count} 个，实际新增 {generated_count} 个")
            return generated_count
            
        except Exception as e:
            logger.error(f"generate_time_slots 方法整体出错: {str(e)}", exc_info=True)
            # 重新抛出异常，让上层捕获返回500错误时附带信息
            raise

    
class ChefProfileView(APIView):
    # 关键：删除 authentication_classes 和 permission_classes，允许匿名访问
    # 身份验证完全通过 openid 实现
    def get(self, request):
        # 从查询参数获取 openid
        openid = request.query_params.get('openid')
        if not openid:
            return Response(
                {"detail": "缺少openid参数"},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 通过 openid 查询厨师记录
        try:
            dj_chef = DjChef.objects.get(openid=openid)
            serializer = DjChefProfileSerializer(dj_chef)
            return Response({"data": serializer.data})
        except DjChef.DoesNotExist:
            return Response(
                {"detail": "未找到该厨师记录"},
                status=status.HTTP_404_NOT_FOUND
            )

class ChefPendingSlotsCountView(APIView):
    """
    厨师待处理档期数量查询接口
    用于前端轮询检测新订单，返回厨师当前处于"已预订"状态的档期数量（今天及以后）
    """
    permission_classes = [permissions.IsAuthenticated]

    @method_decorator(cache_page(5))
    @method_decorator(vary_on_cookie)
    def get(self, request):
        try:
            chef = DjChef.objects.get(user=request.user)
            today = timezone.now().date()  # 获取今天的日期（不含时间）
            
            # 新增date__gte=today条件，只统计今天及以后的已预订档期
            pending_count = TimeSlot.objects.filter(
                chef=chef,
                status='booked',
                date__gte=today  # 关键：只包含日期大于等于今天的档期
            ).count()
            
            last_updated = TimeSlot.objects.filter(
                chef=chef,
                date__gte=today  # 同步限制时间范围
            ).aggregate(
                max_updated=models.Max('updated_at')
            )['max_updated']
            
            return Response({
                'count': pending_count,
                'timestamp': last_updated
            })
            
        except DjChef.DoesNotExist:
            return Response(
                {'error': '厨师信息不存在'},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            logger.error(f"查询待处理档期失败: {str(e)}", exc_info=True)
            return Response(
                {'error': '服务器内部错误'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class ChefRecentPendingSlotsView(APIView):
    """
    厨师最新待处理档期详情接口
    用于获取具体的新订单信息，可配合数量接口使用
    """
    permission_classes = [permissions.IsAuthenticated]

    # 补充缓存装饰器，减少重复查询
    @method_decorator(cache_page(5))
    @method_decorator(vary_on_cookie)
    def get(self, request):
        try:
            chef = DjChef.objects.get(user=request.user)
            
            # 获取最近5条已预订的档期，包含订单信息
            pending_slots = TimeSlot.objects.filter(
                chef=chef,
                status='booked'
            ).select_related('order').order_by('-updated_at')[:5]  # 按更新时间倒序
            
            data = [{
                'slot_id': slot.id,
                'date': slot.date,
                'period': slot.period,
                'order_id': slot.order.id if slot.order else None,
                'order_number': slot.order.order_number if slot.order else None,
                'updated_at': slot.updated_at
            } for slot in pending_slots]
            
            return Response(data)
            
        except DjChef.DoesNotExist:
            return Response(
                {'error': '厨师信息不存在'},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            logger.error(f"查询待处理档期详情失败: {str(e)}", exc_info=True)
            return Response(
                {'error': '服务器内部错误'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

class ChefBackgroundUploadView(generics.CreateAPIView):
    """
    厨师背景图上传视图（直接通过openid查询DjChef，不依赖User模型）
    """
    serializer_class = DjChefProfileSerializer
    # 移除IsAuthenticated权限，改为手动验证openid
    permission_classes = []

    def get_dj_chef(self):
        """通过请求中的openid直接查询DjChef实例"""
        # 从请求数据中获取openid（支持POST表单或JSON）
        openid = self.request.data.get('openid') or self.request.POST.get('openid')
        
        if not openid:
            logger.warning("请求中未包含openid参数")
            raise PermissionDenied(detail='缺少openid参数，无法验证身份')
        
        try:
            # 直接通过openid查询DjChef，不经过User模型
            dj_chef = DjChef.objects.get(openid=openid)
            logger.info(f"通过openid查询到厨师: ID={dj_chef.id}, openid={openid}")
            return dj_chef
        except DjChef.DoesNotExist:
            logger.warning(f"未找到对应厨师，openid={openid}")
            raise PermissionDenied(detail='未找到该厨师信息')

    def post(self, request, *args, **kwargs):
        logger.info("开始处理背景图上传请求")
        print(f"当前MEDIA_ROOT: {settings.MEDIA_ROOT}")
        try:
            # 关键：直接通过openid获取DjChef实例（不依赖request.user）
            dj_chef = self.get_dj_chef()
            
            # 1. 检查文件
            if not request.FILES or 'background' not in request.FILES:
                logger.warning("请求中没有找到背景图片文件")
                return Response(
                    {'error': '请选择背景图片文件', 'status': 'error'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            uploaded_file = request.FILES['background']
            
            # 2. 验证文件类型
            allowed_types = ['image/jpeg', 'image/png']
            if uploaded_file.content_type not in allowed_types:
                logger.warning(f"文件类型不支持: {uploaded_file.content_type}")
                return Response(
                    {'error': '仅支持JPEG/PNG格式图片', 'status': 'error'},
                    status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
                )
            
            # 3. 验证文件大小
            max_size = 5 * 1024 * 1024
            if uploaded_file.size > max_size:
                logger.warning(f"文件大小超限: {uploaded_file.size}字节")
                return Response(
                    {'error': '图片大小不能超过5MB', 'status': 'error'},
                    status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
                )
            
            # 4. 生成唯一文件名（基于DjChef ID）
            file_ext = os.path.splitext(uploaded_file.name)[1].lower()
            new_filename = f"bg_{dj_chef.id}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_ext}"
            
            # 5. 保存文件
            save_dir = os.path.join(settings.MEDIA_ROOT, 'backgrounds')
            os.makedirs(save_dir, exist_ok=True)
            save_path = os.path.join(save_dir, new_filename)
            
            try:
                with open(save_path, 'wb+') as destination:
                    for chunk in uploaded_file.chunks():
                        destination.write(chunk)
                logger.info(f"文件保存成功: {save_path}")
            except IOError as e:
                logger.error(f"文件保存失败: {str(e)}", exc_info=True)
                return Response(
                    {'error': '文件保存失败', 'detail': str(e)},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            
            # 6. 更新DjChef背景图字段（直接操作模型）
            base_url = f"https://{request.get_host()}" if settings.DEBUG else "https://chefshub.cn"
            full_url = f"{base_url}{settings.MEDIA_URL}backgrounds/{new_filename}"
            
            dj_chef.background = full_url
            dj_chef.background_approved = True
            dj_chef.save(update_fields=['background', 'background_approved', 'updated_at'])
            logger.info(f"厨师背景图更新成功: {full_url}")
            
            return Response({
                'url': full_url,
                'filename': new_filename,
                'message': '背景图上传成功',
                'status': 'success',
                'approved': False,
                'dj_chef_id': dj_chef.id
            }, status=status.HTTP_201_CREATED)
            
        except PermissionDenied as e:
            logger.error(f"权限错误: {str(e)}")
            return Response(
                {'error': str(e.detail), 'status': 'error'},
                status=status.HTTP_403_FORBIDDEN
            )
        except Exception as e:
            logger.error(f"服务器处理错误: {str(e)}", exc_info=True)
            return Response(
                {'error': '服务器处理错误', 'detail': str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

class ChefWalletView(generics.RetrieveAPIView):
    """获取厨师钱包信息"""
    serializer_class = ChefWalletSerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        # 直接使用当前登录用户（已认证为DjChef实例）
        dj_chef = self.request.user
        
        # 验证用户类型
        if not isinstance(dj_chef, DjChef):
            raise PermissionDenied(
                detail='当前登录用户不是厨师',
                code='invalid_chef_user'
            )
        
        # 获取关联的钱包信息
        wallet = getattr(dj_chef, 'payment_wallet', None)
        if not wallet:
            raise PermissionDenied(
                detail='厨师钱包信息不存在',
                code='wallet_not_found'
            )
        
        return wallet


class SMSCodeView(APIView):
    """发送短信验证码"""
    def post(self, request):
        serializer = PhoneNumberSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        code = ''.join([str(random.randint(0, 9)) for _ in range(6)])
        
        # 模拟发送短信，实际项目应接入短信服务商API
        print(f"验证码发送至 {phone}: {code}")
        
        # 存储验证码到缓存，5分钟过期
        cache.set(f'sms_{phone}', code, 300)
        
        return Response({'message': '验证码已发送'})

class SMSLoginView(APIView):
    """短信验证码登录"""
    def post(self, request):
        serializer = SMSCodeSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        code = serializer.validated_data['code']
        
        # 验证短信验证码
        cached_code = cache.get(f'sms_{phone}')
        if cached_code != code:
            return Response(
                {'error': '验证码错误或已过期'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 查找或创建用户
        user, created = User.objects.get_or_create(
            username=f'user_{phone}',
            defaults={'phone_number': phone}
        )
        
        # 获取或创建厨师账号
        chef, _ = DjChef.objects.get_or_create(
            phone_number=phone,
            defaults={
                'user': user,
                'nickname': f'用户{phone[-4:]}',
                'verification_status': 'unverified'
            }
        )
        
        # 生成JWT token
        refresh = RefreshToken.for_user(user)
        
        return Response({
            'refresh': str(refresh),
            'access': str(refresh.access_token),
            'user': LoginResponseSerializer(chef).data
        })

class WechatLoginView(APIView):
    logger = logging.getLogger(__name__)
    authentication_classes = []
    permission_classes = []

    def post(self, request):
        """微信小程序登录接口"""
        serializer = WechatLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # 获取前端参数
        code = serializer.validated_data['code']
        encrypted_data = request.data.get('encryptedData')
        iv = request.data.get('iv')
        nick_name = request.data.get('nickName', '微信用户')
        avatar_url = request.data.get('avatarUrl', '')
        
        try:
            # 1. 获取微信配置（使用provider配置）
            appid, secret = self._get_wechat_config('provider')
            if not all([appid, secret]):
                return Response(
                    {'error': '服务器配置错误'}, 
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            
            # 2. 获取微信会话信息
            session_data = self._get_wechat_session(code, appid, secret)
            if isinstance(session_data, Response):
                return session_data  # 返回错误响应
            
            # 3. 处理用户数据
            return self._process_user_data(
                openid=session_data['openid'],
                session_key=session_data.get('session_key'),
                encrypted_data=encrypted_data,
                iv=iv,
                nick_name=nick_name,
                avatar_url=avatar_url,
                appid=appid
            )
            
        except Exception as e:
            logger.error(f"微信登录处理失败: {str(e)}", exc_info=True)
            return Response(
                {'error': '登录处理异常'}, 
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    def _get_wechat_config(self, config_type='provider'):
        """
        获取微信配置
        :param config_type: 'consumer' 或 'provider'
        :return: (appid, secret)
        """
        config = settings.WECHAT_CONFIG.get(config_type, {})
        appid = config.get('appid', '')
        secret = config.get('secret', '')
        
        if not all([appid, secret]):
            logger.error(f"微信{config_type}配置不完整: appid={appid}, secret={secret}")
            return None, None
            
        return appid, secret

    def _get_wechat_session(self, code, appid, secret):
        """获取微信会话密钥"""
        url = f"https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code"
        
        try:
            response = requests.get(url, timeout=5)
            response.raise_for_status()
            data = response.json()
            
            if 'errcode' in data:
                logger.error(f"微信API错误: {data.get('errmsg')} (code: {data['errcode']})")
                return Response(
                    {'error': data['errmsg']},
                    status=status.HTTP_400_BAD_REQUEST
                )
                
            if 'openid' not in data:
                logger.error("微信返回缺少openid")
                return Response(
                    {'error': '无效的微信响应'},
                    status=status.HTTP_400_BAD_REQUEST
                )
                
            # 记录从微信获取的session_key
            logger.debug(f"从微信获取的session_key: {data['session_key'][:20]}...")
            return data
            
        except requests.RequestException as e:
            logger.error(f"微信API请求失败: {str(e)}")
            return Response(
                {'error': '微信服务不可用'},
                status=status.HTTP_503_SERVICE_UNAVAILABLE
            )

    def _decrypt_data(self, encrypted_data, iv, session_key, appid):
        """使用session_key解密用户数据（优化版）"""
        if not all([encrypted_data, iv, session_key]):
            logger.error("缺少必要的解密参数（encrypted_data/iv/session_key）")
            return None
            
        try:
            # 1. 解码密钥、加密数据和IV
            session_key_bytes = base64.b64decode(session_key)
            encrypted_data_bytes = base64.b64decode(encrypted_data)
            iv_bytes = base64.b64decode(iv)
            
            # 验证session_key长度（AES-128/192/256对应16/24/32字节）
            if len(session_key_bytes) not in [16, 24, 32]:
                logger.error(f"无效的session_key长度: {len(session_key_bytes)}字节（需16/24/32字节）")
                return None

            # 2. AES-CBC解密
            cipher = AES.new(session_key_bytes, AES.MODE_CBC, iv_bytes)
            decrypted_bytes = cipher.decrypt(encrypted_data_bytes)
            
            # 3. 验证并去除PKCS#7填充（关键修复点）
            # PKCS#7规则：填充长度为1-16字节，且每个填充字节值等于填充长度
            padding_length = decrypted_bytes[-1]
            if not (1 <= padding_length <= 16):
                raise ValueError(f"无效的PKCS#7填充长度: {padding_length}（需1-16）")
            
            # 验证所有填充字节是否符合规则
            for i in range(1, padding_length + 1):
                if decrypted_bytes[-i] != padding_length:
                    raise ValueError(f"PKCS#7填充验证失败，位置{-i}的值不符")
            
            # 去除填充
            decrypted_bytes = decrypted_bytes[:-padding_length]
            
            # 4. 解码为字符串并验证JSON格式
            try:
                decrypted_str = decrypted_bytes.decode('utf-8')
            except UnicodeDecodeError as e:
                logger.error(f"解密数据不是UTF-8格式: {binascii.hexlify(decrypted_bytes[:32])}...")
                return None
            
            # 空字符串直接返回失败
            if not decrypted_str.strip():
                logger.error("解密后的数据为空字符串")
                return None
            
            # 解析JSON
            try:
                decrypted_dict = json.loads(decrypted_str)
            except json.JSONDecodeError as e:
                logger.error(f"JSON解析失败: {str(e)}，原始数据: {decrypted_str[:100]}...")
                return None
            
            # 5. 验证appid一致性
            if decrypted_dict.get('watermark', {}).get('appid') != appid:
                logger.error(f"appid不匹配: 解密数据={decrypted_dict.get('watermark', {}).get('appid')}, 配置={appid}")
                return None
            
            return decrypted_dict
            
        except binascii.Error as e:
            logger.error(f"Base64解码失败: {str(e)}")
            return None
        except (ValueError, TypeError) as e:
            logger.error(f"AES解密或填充验证失败: {str(e)}")
            raise ValueError("Failed to decrypt data") from e
        except Exception as e:
            logger.error(f"数据解密过程异常: {str(e)}", exc_info=True)
            return None

    def _process_user_data(self, openid, encrypted_data=None, iv=None, appid=None, session_key=None, nick_name=None, avatar_url=None):
        """处理用户数据并返回登录响应"""
        try:
            from .models import DjChef  # 确保导入DjChef模型
            
            # 尝试通过 openid 查找 DjChef 对象
            try:
                dj_chef = DjChef.objects.get(openid=openid)
            except DjChef.DoesNotExist:
                # 创建新的DjChef对象，不依赖User.username的唯一性
                # 创建关联的User对象（如果需要）
                # 生成唯一的username，仅用于内部管理
                unique_username = f"wx_{uuid.uuid4().hex[:12]}"
                
                # 创建User对象（如果需要）
                user = User.objects.create(
                    username=unique_username,
                    is_active=True,
                    is_staff=False,
                    is_superuser=False
                )
                
                # 创建DjChef对象
                dj_chef = DjChef.objects.create(
                    user=user,
                    openid=openid,
                    # 设置其他必要的默认字段
                    username=nick_name or f"用户_{uuid.uuid4().hex[:4]}"
                )
            
            # 更新用户信息
            if nick_name:
                dj_chef.username = nick_name  # 使用DjChef的username字段
            if avatar_url:
                dj_chef.avatar = avatar_url
            
            # 如果有加密数据，尝试解密（获取手机号）
            if encrypted_data and iv and session_key:
                decrypted_data = self._decrypt_data(encrypted_data, iv, session_key, appid)
                if decrypted_data and 'phoneNumber' in decrypted_data:
                    dj_chef.phone_number = decrypted_data['phoneNumber']
            
            dj_chef.save()
            
            # 生成token（使用DjChef关联的User对象）

            #token, created = Token.objects.get_or_create(user=dj_chef.user)
            # 生成 JWT token
            refresh = RefreshToken.for_user(dj_chef.user)
            # 构建返回数据
            return Response({
                'access': str(refresh.access_token),
                'refresh': str(refresh),  # 如果使用JWT，这里应返回refresh token
                'user': {
                    'id': dj_chef.id,  # 返回DjChef的ID而非User的ID
                    'username': dj_chef.username,  # 使用DjChef的username
                    'nick_name': dj_chef.username,  # 使用DjChef的username作为昵称
                    'avatar_url': dj_chef.avatar or '',
                    'phone_number': dj_chef.phone_number or '',
                    'openid': dj_chef.openid,
                    # 其他DjChef字段...
                }
            }, status=status.HTTP_200_OK)
            
        except Exception as e:
            logger.error(f"处理用户数据失败: {str(e)}", exc_info=True)
            return Response(
                {'error': '登录处理异常'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    def _update_user_profile(self, user, user_info):
        """更新用户资料（兼容旧代码）"""
        if not user_info:
            return
            
        if 'avatarUrl' in user_info and user_info['avatarUrl']:
            if hasattr(user, 'djchef'):
                if user.djchef.avatar != user_info['avatarUrl']:
                    user.djchef.avatar = user_info['avatarUrl']
                    user.djchef.save()
    
class ChangePhoneView(APIView):
    """更换手机号接口"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        serializer = ChangePhoneSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # 验证短信验证码
        phone = serializer.validated_data['new_phone']
        cached_code = cache.get(f'change_phone_{phone}')
        if cached_code != serializer.validated_data['sms_code']:
            return Response(
                {'error': '验证码错误或已过期'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 更新手机号
        chef = request.user.djchef
        chef.phone_number = phone
        chef.save()
        
        return Response({
            'message': '手机号更换成功',
            'new_phone': phone
        })

class SendChangePhoneSMSView(APIView):
    """发送换绑手机验证码"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        phone = request.data.get('phone')
        if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
            return Response(
                {'error': '手机号格式不正确'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 检查是否已注册
        if DjChef.objects.filter(phone_number=phone).exists():
            return Response(
                {'error': '该手机号已被注册'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 生产环境从前端获取验证码，开发环境生成
        if settings.DEBUG:
            code = ''.join([str(random.randint(0, 9)) for _ in range(6)])
            cache.set(f'change_phone_{phone}', code, 300)
            print(f'[DEBUG] 验证码发送至 {phone}: {code}')
        else:
            code = request.data.get('code')
            if not code:
                return Response(
                    {'error': '验证码不能为空'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            cache.set(f'change_phone_{phone}', code, 300)
        
        return Response({'message': '验证码已发送'})
    
class ChefsAvatarUploadView(APIView):
    """厨师头像上传API（完全依赖DjChef模型，不关联User）"""
    # 移除IsAuthenticated权限，通过openid手动验证
    permission_classes = []
    
    def post(self, request):
        try:
            # 1. 通过openid获取DjChef实例（核心修改：替代request.user）
            openid = request.data.get('openid') or request.POST.get('openid')
            if not openid:
                logger.warning("头像上传请求缺少openid参数")
                return Response(
                    {'error': '缺少openid参数，无法验证身份', 'status': 'error'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            try:
                dj_chef = DjChef.objects.get(openid=openid)
                logger.info(f"通过openid验证厨师身份：ID={dj_chef.id}")
            except DjChef.DoesNotExist:
                logger.warning(f"openid对应的厨师不存在：{openid}")
                return Response(
                    {'error': '未找到该厨师信息', 'status': 'error'},
                    status=status.HTTP_404_NOT_FOUND
                )
            
            # 2. 检查文件是否存在
            if 'avatar' not in request.FILES:
                return Response(
                    {'error': '请提供头像文件', 'status': 'error'}, 
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            avatar_file = request.FILES['avatar']
            
            # 3. 验证文件类型
            allowed_types = ['image/jpeg', 'image/png']
            if avatar_file.content_type not in allowed_types:
                return Response(
                    {'error': '只支持JPEG/PNG格式图片', 'status': 'error'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            # 4. 验证文件大小 (5MB限制)
            max_size = 5 * 1024 * 1024
            if avatar_file.size > max_size:
                return Response(
                    {'error': '图片大小不能超过5MB', 'status': 'error'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            # 5. 生成唯一文件名（使用DjChef的ID）
            file_ext = os.path.splitext(avatar_file.name)[1].lower()
            new_filename = f"avatar_{dj_chef.id}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_ext}"
            
            # 6. 确保保存目录存在
            save_dir = os.path.join(settings.MEDIA_ROOT, 'avatars')
            try:
                os.makedirs(save_dir, exist_ok=True)
            except OSError as e:
                return Response(
                    {'error': '无法创建保存目录', 'detail': str(e), 'status': 'error'},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            
            save_path = os.path.join(save_dir, new_filename)
            
            # 7. 保存文件并验证
            try:
                with open(save_path, 'wb+') as destination:
                    for chunk in avatar_file.chunks():
                        destination.write(chunk)
                
                # 验证文件是否确实保存
                if not os.path.exists(save_path):
                    return Response(
                        {'error': '文件保存失败', 'status': 'error'},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR
                    )
                
                # 8. 更新DjChef的头像字段（直接操作模型）
                # 生成完整URL并存储（而非仅文件名）
                if settings.DEBUG:
                    base_url = f"https://{request.get_host()}"
                else:
                    base_url = "https://chefshub.cn"
                avatar_url = f"{base_url}{settings.MEDIA_URL}avatars/{new_filename}"
                
                dj_chef.avatar = avatar_url  # 存储完整URL
                dj_chef.save(update_fields=['avatar', 'updated_at'])  # 仅更新必要字段
                logger.info(f"厨师头像更新成功：{avatar_url}")
                
                return Response({
                    'url': avatar_url,
                    'filename': new_filename,
                    'message': '头像上传成功',
                    'status': 'success',
                    'dj_chef_id': dj_chef.id
                }, status=status.HTTP_201_CREATED)
                
            except IOError as e:
                return Response(
                    {'error': '文件写入失败', 'detail': str(e), 'status': 'error'},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            
        except Exception as e:
            logger.error(f"头像上传处理失败：{str(e)}", exc_info=True)
            return Response(
                {'error': '服务器处理错误', 'detail': str(e), 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

class VerificationImageView(APIView):
    """上传认证图片并同步更新DjChefVerification模型"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        # 打印所有接收的参数（调试用）
        print("\n===== 开始处理认证图片上传 =====")
        print("请求数据(request.data):", request.data)
        print("查询参数(request.query_params):", request.query_params)
        
        # 1. 获取必要参数
        openid = request.data.get('openid') or request.query_params.get('openid')
        image_type = request.data.get('image_type')
        
        # 打印获取到的关键参数
        print(f"提取到的openid: {openid}")
        print(f"提取到的image_type: {image_type}")
        
        # 验证必填参数
        if not openid:
            print("错误: 未获取到openid参数")
            return Response(
                {'error': '缺少openid参数', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        if not image_type:
            print("错误: 未获取到image_type参数")
            return Response(
                {'error': '缺少image_type参数（可选值：id_card_front, id_card_back, health_certificate, chef_certificate, passport_image）', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 验证图片类型合法性
        valid_image_types = [
            'id_card_front', 'id_card_back', 
            'health_certificate', 'chef_certificate',
            'passport_image'
        ]
        if image_type not in valid_image_types:
            print(f"错误: 无效的image_type={image_type}，合法值={valid_image_types}")
            return Response(
                {'error': f'无效的image_type，可选值：{valid_image_types}', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 2. 获取对应的厨师实例
        try:
            dj_chef = DjChef.objects.get(openid=openid)
            print(f"找到对应的DjChef: id={dj_chef.id}, nickname={dj_chef.nickname}")
        except DjChef.DoesNotExist:
            print(f"错误: 未找到openid={openid}对应的DjChef")
            return Response(
                {'error': '未找到对应的厨师信息', 'status': 'error'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        # 3. 验证并保存图片文件
        print("开始验证图片数据...")
        serializer = VerificationImageSerializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            image = serializer.validated_data['image']
            print(f"图片验证成功: 文件名={image.name}, 大小={image.size}字节")
        except Exception as e:
            print(f"图片验证失败: {str(e)}")
            return Response(
                {'error': f'图片验证失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 生成文件名
        file_ext = os.path.splitext(image.name)[1].lower()
        new_filename = f"{image_type}_{dj_chef.id}_{openid}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_ext}"
        
        # 关键修复：使用os.path函数构建路径，自动适配操作系统
        base_url = "https://chefshub.cn/media/verification/"
        save_dir = os.path.join(settings.MEDIA_ROOT, 'verification')
        save_path = os.path.normpath(os.path.join(save_dir, new_filename))  # 添加normpath处理
        
        print(f"准备保存图片: 标准化路径={save_path}")
        
        # 确保保存目录存在
        os.makedirs(save_dir, exist_ok=True)
        
        # 保存文件到本地
        try:
            with open(save_path, 'wb+') as destination:
                for chunk in image.chunks():
                    destination.write(chunk)
            print(f"图片保存成功: 路径={save_path}")
        except Exception as e:
            print(f"图片保存失败: {str(e)}")
            return Response(
                {'error': f'图片保存失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
        
        # 4. 同步更新DjChefVerification模型
        try:
            verification, created = DjChefVerification.objects.get_or_create(chef=dj_chef)
            if created:
                print(f"创建新的DjChefVerification记录: id={verification.id}")
            else:
                print(f"找到已存在的DjChefVerification记录: id={verification.id}")
            
            # 关键修复：使用Django存储系统处理文件，避免路径问题
            with open(save_path, 'rb') as f:
                # 使用相对路径而非绝对路径
                relative_path = os.path.relpath(save_path, settings.MEDIA_ROOT)
                django_file = File(f, name=relative_path)  # 只传递相对路径
                setattr(verification, image_type, django_file)
                verification.save()
            print(f"成功更新字段: {image_type} 到 verification.id={verification.id}")
        except Exception as e:
            print(f"更新DjChefVerification失败: {str(e)}")
            return Response(
                {'error': f'更新认证记录失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
        
        # 5. 返回结果
        response_data = {
            'url': f"{base_url}{new_filename}",#f"/media/verification/{new_filename}",
            'message': '上传成功并已同步到认证记录',
            'dj_chef_id': dj_chef.id,
            'openid': openid,
            'verification_id': verification.id,
            'updated_field': image_type
        }
        print("处理完成，返回数据:", response_data)
        print("===== 认证图片上传处理结束 =====")
        
        return Response(response_data)


class SubmitVerificationView(APIView):
    """提交认证申请（修复500错误：NoneType切片问题 + bio字段正确更新）"""
    permission_classes = [AllowAny]
    
    def post(self, request):
        logger.info("\n===== 开始处理认证提交 =====")
        
        # 1. 提取并验证openid
        openid = request.data.get('openid') or request.query_params.get('openid')
        logger.info(f"提取到的openid: {openid[:6]}***（类型: {type(openid).__name__}）")
        
        if not openid or not isinstance(openid, str):
            logger.error("400错误：未提供有效openid（为空或非字符串）")
            return Response(
                {'error': '缺少或无效的openid参数（需为字符串）', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 2. 查询厨师信息
        try:
            dj_chef = DjChef.objects.get(openid=openid)
            logger.info(f"查询到厨师信息：ID={dj_chef.id}, openid={dj_chef.openid[:6]}***")
        
        except DjChef.DoesNotExist:
            logger.warning(f"404错误：未找到openid={openid[:6]}***对应的厨师账号")
            return Response(
                {'error': '该账号不是厨师账号，请先注册为厨师', 'status': 'error'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        except Exception as e:
            logger.error(f"500错误：查询厨师信息异常 - {str(e)}", exc_info=True)
            return Response(
                {'error': '服务器查询厨师信息失败', 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
        
        # 3. 检查是否已通过认证
        if dj_chef.verification_status == 'verified':
            logger.warning(f"400错误：厨师ID={dj_chef.id}已通过认证，重复提交")
            return Response(
                {'error': '您已通过认证，无需重复提交', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 4. 验证提交数据
        serializer = VerificationSubmitSerializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            logger.info(f"序列化器验证成功：有效数据={serializer.validated_data}")
        
        except Exception as e:
            logger.error(f"400错误：数据验证失败 - {str(e)}")
            return Response(
                {'error': f'数据验证失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 5. 创建/更新认证记录
        try:
            verification, created = DjChefVerification.objects.update_or_create(
                chef=dj_chef,
                defaults=serializer.validated_data
            )
            action = "创建" if created else "更新"
            logger.info(f"{action}认证记录成功：ID={verification.id}, 厨师ID={dj_chef.id}")
        
        except Exception as e:
            logger.error(f"500错误：{action}认证记录异常 - {str(e)}", exc_info=True)
            return Response(
                {'error': f'服务器处理认证记录失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
        
        # 6. 更新厨师信息及状态（核心修复：处理None值切片问题）
        try:
            # 记录更新前的字段值（关键：对None值做默认处理）
            old_real_name = dj_chef.real_name or "未设置"
            old_specialty = dj_chef.specialty or "未设置"
            old_bio = dj_chef.bio or "未设置"  # 若为None，替换为"未设置"
            old_verification_status = dj_chef.verification_status
            
            # 更新real_name（去空格+非空判断）
            if 'real_name' in request.data:
                real_name = str(request.data['real_name']).strip()
                if real_name:
                    dj_chef.real_name = real_name
            
            # 更新specialty（去空格+非空判断）
            if 'specialty' in request.data:
                specialty = str(request.data['specialty']).strip()
                if specialty:
                    dj_chef.specialty = specialty
            
            # 更新bio（核心：正确赋值+处理None）
            if 'bio' in request.data:
                bio = str(request.data['bio']).strip()  # TextField允许空字符串，保留原始输入
                dj_chef.bio = bio  # 正确赋值给bio字段
            
            # 更新审核状态和提交时间
            dj_chef.verification_status = 'pending'
            dj_chef.verification_submitted_at = timezone.now()
            
            # 保存更新（指定字段，减少数据库开销）
            dj_chef.save(update_fields=[
                'real_name', 'specialty', 'bio',
                'verification_status', 'verification_submitted_at'
            ])
            
            # 修复：对更新后的bio做None处理，避免切片报错
            new_bio = dj_chef.bio or "未设置"
            # 日志输出（切片前先判断是否为字符串，避免None）
            logger.info(
                f"厨师信息更新成功（ID={dj_chef.id}）：\n"
                f"real_name: {old_real_name} → {dj_chef.real_name or '未设置'}\n"
                f"specialty: {old_specialty} → {dj_chef.specialty or '未设置'}\n"
                f"bio: {old_bio[:20] if isinstance(old_bio, str) else old_bio}*** → "
                f"{new_bio[:20] if isinstance(new_bio, str) else new_bio}***\n"
                f"verification_status: {old_verification_status} → pending"
            )
        
        except Exception as e:
            logger.error(f"500错误：更新厨师信息异常 - {str(e)}", exc_info=True)
            return Response(
                {'error': f'更新厨师信息失败: {str(e)}', 'status': 'error'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
        
        # 7. 返回成功响应
        response_data = {
            'message': '认证申请已提交，等待审核',
            'status': dj_chef.verification_status,
            'dj_chef_id': dj_chef.id,
            'submitted_at': dj_chef.verification_submitted_at.strftime("%Y-%m-%d %H:%M:%S"),
            'updated_fields': {
                'real_name': dj_chef.real_name or '未设置',
                'specialty': dj_chef.specialty or '未设置',
                'bio': dj_chef.bio[:50] + "..." if (isinstance(dj_chef.bio, str) and len(dj_chef.bio) > 50) else (dj_chef.bio or '未设置')
            }
        }
        
        return Response(response_data, status=status.HTTP_200_OK)


class VerificationStatusView(APIView):
    def get(self, request):
        openid = request.query_params.get('openid')
        if not openid:
            return Response(
                {"detail": "缺少openid参数"},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        try:
            dj_chef = DjChef.objects.get(openid=openid)
            return Response({
                "data": {
                    "verification_status": dj_chef.verification_status,
                    "chef_id": dj_chef.id,
                    #"is_chef": dj_chef.verification_status == "verified"
                    "is_chef": True

                }
            })
        except DjChef.DoesNotExist:
            return Response(
                {"detail": "未找到该厨师记录"},
                status=status.HTTP_404_NOT_FOUND
            )
    

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def sync_subscribe_status(request):
    """
    同步厨师的微信订阅状态到后端
    请求体参数:
    - template_id: 订阅消息模板ID
    - wechat_real_status: 微信返回的实时状态 (accept/reject/ban)
    - sync_time: 前端同步时间 (ISO格式字符串)
    """
    try:
        # 1. 验证请求用户是否为厨师
        try:
            chef = DjChef.objects.get(user=request.user)
            logger.debug(f"验证通过，厨师ID: {chef.id}, 用户ID: {request.user.id}")
        except DjChef.DoesNotExist:
            logger.warning(f"用户{request.user.id}不是厨师，尝试同步订阅状态")
            return Response(
                {"success": False, "error": "仅厨师可操作"}, 
                status=403
            )
        
        # 2. 验证请求参数（增加参数完整性校验）
        required_fields = ['template_id', 'wechat_real_status']
        missing_fields = [field for field in required_fields if field not in request.data]
        if missing_fields:
            error_msg = f"缺少必要参数: {missing_fields}"
            logger.warning(f"厨师{chef.id}同步失败: {error_msg}")
            return Response(
                {"success": False, "error": error_msg}, 
                status=400
            )
        
        template_id = request.data['template_id']
        wechat_status = request.data['wechat_real_status']
        
        # 验证参数格式（增加模板ID长度校验）
        if not isinstance(template_id, str) or len(template_id) < 10:
            error_msg = "无效的template_id（格式错误）"
            logger.warning(f"厨师{chef.id}同步失败: {error_msg}, 实际值: {template_id}")
            return Response(
                {"success": False, "error": error_msg}, 
                status=400
            )
        
        # 验证微信状态合法性
        valid_statuses = ['accept', 'reject', 'ban']
        if wechat_status not in valid_statuses:
            error_msg = f"无效的状态值，必须是{valid_statuses}"
            logger.warning(f"厨师{chef.id}同步失败: {error_msg}, 实际值: {wechat_status}")
            return Response(
                {"success": False, "error": error_msg}, 
                status=400
            )
        
        # 3. 更新厨师订阅状态（增加事务保证）
        from django.db import transaction
        with transaction.atomic():
            # 记录原始状态用于日志对比
            original_status = chef.subscribe_status
            
            # 更新状态（仅当微信状态为'accept'时标记为已订阅）
            chef.subscribe_status = (wechat_status == 'accept')
            chef.updated_at = timezone.now()
            chef.save(update_fields=['subscribe_status', 'updated_at'])
            
            # 验证保存结果
            updated_chef = DjChef.objects.get(id=chef.id)
            if updated_chef.subscribe_status != chef.subscribe_status:
                raise Exception("状态更新后验证失败，可能存在并发修改")
        
        # 4. 记录成功日志（包含详细前后状态对比）
        logger.info(
            f"厨师{chef.id}订阅状态同步成功 "
            f"模板ID: {template_id}, "
            f"微信状态: {wechat_status}, "
            f"更新前状态: {original_status}, "
            f"更新后状态: {chef.subscribe_status}, "
            f"同步时间: {timezone.now().isoformat()}"
        )
        
        # 5. 返回详细响应（包含前后状态便于前端调试）
        return Response({
            "success": True,
            "current_subscribe_status": chef.subscribe_status,
            "previous_subscribe_status": original_status,
            "template_id": template_id,
            "received_wechat_status": wechat_status,
            "sync_time": timezone.now().isoformat()
        })
    
    # 捕获所有未处理异常（关键：避免前端收到500但无错误信息）
    except Exception as e:
        # 记录异常堆栈信息（使用logger.exception自动包含堆栈）
        logger.exception(
            f"厨师订阅状态同步发生未预期错误 "
            f"用户ID: {request.user.id}, "
            f"请求数据: {request.data}"
        )
        return Response(
            {
                "success": False,
                "error": f"服务器处理失败: {str(e)}",
                "error_type": type(e).__name__
            }, 
            status=500
        )


#厨师小程序后台部分

class VdjChefListView(generics.ListAPIView):
    queryset = Chef.objects.all()
    serializer_class = VdjChefSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ChefFilter

    def get_queryset(self):
        queryset = super().get_queryset()
        city = self.request.query_params.get('city')
        
        if city:
            queryset = queryset.filter(city__iexact=city)
            
        return queryset

class VdjChefDetailView(generics.RetrieveAPIView):
    queryset = Chef.objects.all().select_related('user').prefetch_related('images', 'certificates')
    serializer_class = VdjChefDetailSerializer
    lookup_field = 'id'

class VdjChefReviewCreateView(generics.CreateAPIView):
    queryset = ChefReview.objects.all()
    serializer_class = VdjChefReviewSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        chef = Chef.objects.get(id=self.kwargs['chef_id'])
        serializer.save(user=self.request.user, chef=chef)
        
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        chef.save()

class VdjChefReviewUpdateDeleteView(generics.RetrieveUpdateDestroyAPIView):
    queryset = ChefReview.objects.all()
    serializer_class = VdjChefReviewSerializer
    permission_classes = [IsReviewOwnerOrReadOnly]
    lookup_field = 'id'

    def perform_update(self, serializer):
        instance = serializer.save()
        chef = instance.chef
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        chef.save()

    def perform_destroy(self, instance):
        chef = instance.chef
        instance.delete()
        reviews = chef.reviews.all()
        chef.review_count = reviews.count()
        if chef.review_count > 0:
            chef.rating = sum([review.rating for review in reviews]) / chef.review_count
        else:
            chef.rating = 0
        chef.save()

class VdjChefCategoryListView(generics.ListAPIView):
    serializer_class = VdjDishCategorySerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_queryset(self):
        chef_id = self.kwargs['chef_id']
        return DishCategory.objects.filter(
            dishes__chef_id=chef_id
        ).distinct().order_by('name')

class VdjChefDishListView(generics.ListAPIView):
    serializer_class = VdjDishSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category']

    def get_queryset(self):
        chef_id = self.kwargs['chef_id']
        return Dish.objects.filter(
            chef_id=chef_id,
            is_available=True
        ).select_related('category').order_by('name')

class VdjChefProfileView(generics.RetrieveUpdateAPIView):
    serializer_class = VdjChefProfileSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        return self.request.user.djchef

class VdjChefBackgroundUploadView(generics.UpdateAPIView):
    serializer_class = VdjChefProfileSerializer
    permission_classes = [permissions.IsAuthenticated]

    def update(self, request, *args, **kwargs):
        chef = request.user.djchef
        if 'background' not in request.FILES:
            return Response(
                {'error': 'No background image provided'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        chef.background = request.FILES['background']
        chef.background_approved = False
        chef.save()
        
        serializer = self.get_serializer(chef)
        return Response(serializer.data)

class VdjSMSCodeView(APIView):
    def post(self, request):
        serializer = VdjPhoneNumberSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        code = ''.join([str(random.randint(0, 9)) for _ in range(6)])
        
        print(f"验证码发送至 {phone}: {code}")
        cache.set(f'sms_{phone}', code, 300)
        
        return Response({'message': '验证码已发送'})

class VdjSMSLoginView(APIView):
    def post(self, request):
        serializer = VdjSMSCodeSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['phone']
        code = serializer.validated_data['code']
        
        cached_code = cache.get(f'sms_{phone}')
        if cached_code != code:
            return Response(
                {'error': '验证码错误或已过期'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        
        user, created = get_user_model().objects.get_or_create(
            username=f'user_{phone}',
            defaults={'phone_number': phone}
        )
        
        chef, _ = DjChef.objects.get_or_create(
            phone_number=phone,
            defaults={
                'user': user,
                'nickname': f'用户{phone[-4:]}',
                'verification_status': 'unverified'
            }
        )
        
        refresh = RefreshToken.for_user(user)
        
        return Response({
            'refresh': str(refresh),
            'access': str(refresh.access_token),
            'user': VdjLoginResponseSerializer(chef).data
        })

class VdjWechatLoginView(APIView):
    def post(self, request):
        serializer = VdjWechatLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        code = serializer.validated_data['code']
        openid = f"mock_openid_{code[:10]}"
        
        chef, created = DjChef.objects.get_or_create(
            openid=openid,
            defaults={
                'nickname': f'微信用户{openid[-4:]}',
                'verification_status': 'unverified'
            }
        )
        
        refresh = RefreshToken.for_user(chef.user)
        
        return Response({
            'refresh': str(refresh),
            'access': str(refresh.access_token),
            'user': VdjLoginResponseSerializer(chef).data
        })

class VdjChangePhoneView(APIView):
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        serializer = VdjChangePhoneSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        phone = serializer.validated_data['new_phone']
        cached_code = cache.get(f'change_phone_{phone}')
        if cached_code != serializer.validated_data['sms_code']:
            return Response(
                {'error': '验证码错误或已过期'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        
        chef = request.user.djchef
        chef.phone_number = phone
        chef.save()
        
        return Response({
            'message': '手机号更换成功',
            'new_phone': phone
        })

class VdjSendChangePhoneSMSView(APIView):
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        phone = request.data.get('phone')
        if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
            return Response(
                {'error': '手机号格式不正确'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        code = ''.join([str(random.randint(0, 9)) for _ in range(6)])
        cache.set(f'change_phone_{phone}', code, 300)
        print(f'[DEBUG] 验证码发送至 {phone}: {code}')
        
        return Response({'message': '验证码已发送'})

class VdjVerificationImageView(APIView):
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        serializer = VdjVerificationImageSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        image = serializer.validated_data['image']
        file_ext = os.path.splitext(image.name)[1]
        new_filename = f"verify_{request.user.id}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_ext}"
        save_path = os.path.join(settings.MEDIA_ROOT, 'verification', new_filename)
        
        with open(save_path, 'wb+') as destination:
            for chunk in image.chunks():
                destination.write(chunk)
        
        return Response({
            'url': f"/media/verification/{new_filename}",
            'message': '上传成功'
        })

class VdjSubmitVerificationView(APIView):
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        chef = request.user.djchef
        if chef.verification_status == 'verified':
            return Response(
                {'error': '您已通过认证'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        serializer = VdjVerificationSubmitSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        verification, created = DjChefVerification.objects.update_or_create(
            chef=chef,
            defaults=serializer.validated_data
        )
        
        chef.verification_status = 'pending'
        chef.verification_submitted_at = datetime.now()
        chef.save()
        
        return Response({
            'message': '认证申请已提交',
            'status': chef.verification_status
        })

class VdjVerificationStatusView(APIView):
    permission_classes = [IsAuthenticated]
    
    def get(self, request):
        chef = request.user.djchef
        serializer = VdjVerificationStatusSerializer(chef)
        return Response(serializer.data)
    


#厨师身份认证后台管理


class VdjChefAuthListView(generics.ListAPIView):
    """待审核厨师列表"""
    queryset = DjChef.objects.filter(verification_status='pending')
    serializer_class = VdjChefVerificationSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter]
    filterset_fields = ['verification_status']
    search_fields = ['nickname', 'real_name', 'phone_number']

class VdjChefAuthDetailView(generics.RetrieveUpdateAPIView):
    """认证详情及审核操作"""
    queryset = DjChef.objects.select_related('verification_info', 'profile').all()
    serializer_class = VdjChefVerificationSerializer
    lookup_field = 'id'

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        action = request.data.get('action')
        
        if action == 'approve':
            # 更新DjChef状态
            instance.verification_status = 'verified'
            instance.verification_processed_at = timezone.now()
            instance.save()
            
            # 同步到Chef表
            self.sync_to_chef_model(instance)
            
            return Response({'message': '认证已通过'})
            
        elif action == 'reject':
            instance.verification_status = 'rejected'
            instance.verification_processed_at = timezone.now()
            instance.save()
            
            # 如果拒绝认证，确保不在消费端展示
            Chef.objects.filter(djchef=instance).delete()
            
            return Response({'message': '认证已拒绝'})
        else:
            return Response(
                {'error': '无效操作'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
    
    def sync_to_chef_model(self, djchef):
        """将认证通过的厨师数据同步到Chef表"""
        defaults = {
            'user': djchef.user,
            'name': djchef.real_name or djchef.nickname,
            'avatar': djchef.avatar,
            'specialty': djchef.verification_info.specialty if hasattr(djchef, 'verification_info') else '',
            'bio': djchef.profile.bio if hasattr(djchef, 'profile') else '',
            'city': djchef.profile.city if hasattr(djchef, 'profile') else '',
            'is_verified': True,
            'status': 'active' if djchef.user_status == 'active' else 'suspended'
        }
        
        # 使用update_or_create确保数据同步
        Chef.objects.update_or_create(
            djchef=djchef,
            defaults=defaults
        )
    
    def get_serializer_context(self):
        context = super().get_serializer_context()
        context['request'] = self.request
        return context

    
class VdjChefAuthVerifyView(APIView):
    """认证审核操作视图"""
    permission_classes = [IsAuthenticated]

    def post(self, request, pk):
        chef = get_object_or_404(DjChef, pk=pk)
        
        # 添加上下文用于验证
        serializer = ChefAuthVerifySerializer(
            data=request.data,
            context={'chef': chef, 'request': request}
        )
        
        serializer.is_valid(raise_exception=True)

        # 创建审核记录
        VerificationRecord.objects.create(
            chef=chef,
            admin=request.user,
            **{
                k: v for k, v in serializer.validated_data.items()
                if k.endswith('_verified')
            }
        )

        # 更新认证状态
        chef.verification_status = 'verified' if serializer.validated_data['approve'] else 'rejected'
        chef.save()

        return Response(
            {'status': 'success'},
            status=status.HTTP_200_OK
        )
    
class IdCardUploadView(APIView):
    """身份证图片专用上传接口"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        chef = request.user.djchef
        image_type = request.data.get('type')  # front/back
        image_file = request.FILES['image']
        
        if image_type not in ['front', 'back']:
            return Response(
                {'error': '类型必须为 front 或 back'},
                status=status.HTTP_400_BAD_REQUEST
            )

        verification, _ = DjChefVerification.objects.get_or_create(chef=chef)
        setattr(verification, f'id_card_{image_type}', image_file)
        verification.save()
        
        return Response({
            'url': getattr(verification, f'id_card_{image_type}').url,
            'message': '上传成功'
        })